import java.io.EOFException;
import java.io.FileNotFoundException;

import neurones.Param;
import neurones.Reseau;

import dauphine.util.Console;

/*
 * ComputerPlayer.java
 *
 * Created on 21 novembre 2003, 21:05
 */

/**
 *
 * @author  liva & students
 */
public class ComputerPlayer {
	/** Couleur du computer Player */
	private int    color;

	/** Le meilleur coup
	 * variable d'instance car
	 * alphaBeta renvoie un score (et pas un coup)
	 */
	public int    bestMove;

	/** Score correspondant au meilleur coup */
	public double bestScore;

	/** Temporaire */
	public int     myDepth;
	public boolean go;
	public int     scannedPositions;
	public int     transpo;
	public int     collision;

	/** Pour la table de transposition */
	public int     hashBitNumbers;
	public int     hashKeyMask;

	//public long hashLockMask;
	public long[]   hashLock;
	public int[]    relativeLevel;
	public byte[]   boundType;
	public double[] score;

	public static final byte LOWER_BOUND=0;
	public static final byte UPPER_BOUND=1;
	public static final byte EXACT_VALUE=2;
	
	public Reseau r;

	/** static ordering */
	private static final int MOVE_INDEX[] =       { 0, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99,  1, 54, 46, 37, 38, 47, 55,  2, 99,
		99, 53, 59, 28, 19, 20, 29, 60, 56, 99,
		99, 45, 27, 13,  5,  6, 14, 30, 48, 99,
		99, 36, 18, 12, 99, 99,  7, 21, 39, 99,
		99, 35, 17, 11, 99, 99,  8, 22, 40, 99,
		99, 44, 26, 16, 10,  9, 15, 31, 41, 99,
		99, 52, 58, 25, 24, 23, 32, 57, 49, 99,
		99,  4, 51, 43, 34, 33, 42, 50,  3, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99};

	private static final int ORDERING[] =         {0,
		11, 18, 88, 81,            // Corners
		34, 35, 46, 56, 65, 64, 53, 43,
		33, 36, 66, 63, 
		52, 42, 24, 25, 47, 57, 75, 74,
		73, 62, 32, 23, 26, 37, 67, 76,
		85, 84, 51, 41, 14, 15, 48, 58,
		68, 86, 83, 61, 31, 13, 16, 38,
		78, 87, 82, 71, 21, 12, 17, 28, 77, 72 , 22, 27
	};
	public static final int MAX_MOVE = 60;

	public static WtdParser parseur=null;

	public static void main(String [] args){
		int theMove = 1;
		for(int i=0; i<=MAX_MOVE; i++){
		}
	}

	/** Le plateau de jeu */
	static Board board;

	
	
	/** Creates a new instance of ComputerPlayer */
	public ComputerPlayer(Board board,int hBN, String file, Reseau rezo) {
		this(Board.BLACK,board,hBN, file, rezo);
		
	}

	/** Autre constructeur */
	public ComputerPlayer(int color, Board board,int hBN, String filename, Reseau rezo){
		r=rezo;
		if(hBN>30){
			System.out.println("Table de hachage trop grande ... arret du programme");
			System.exit(0);
		}
		hashBitNumbers = hBN;
		hashKeyMask    = (1<<hashBitNumbers)-1;
		//hashLockMask=Long.MAX_VALUE-hashKeyMask;
		hashLock       = new long[1<<hashBitNumbers];
		relativeLevel  = new int[1<<hashBitNumbers];
		boundType      = new byte[1<<hashBitNumbers];
		score          = new double[1<<hashBitNumbers];
		this.color     = color;
		this.board     = board;
		
		if(filename!=null && parseur==null){
			try{parseur = new WtdParser(filename);}
			catch(FileNotFoundException e){System.out.println("Erreur le fichier n'existe pas.");}
		} 

	}

	/** Retourne le prochain coup possible a partir
	 * du coup specifie en parametre
	 * Hyper basique -- pas d'ordonnancement (A MODIFIER)
	 * pas dans board car ordre de parcours non trivial (dans la suite)
	 */
	public int nextMove(int player,int move){
		if(move == -1) return -1;

		int theMove = -1;

		/*
         int i       = 1+(move>>Board.moveShift[Board.DIRECTIONS] & 127);
         for(;i<=Board.I_MAX;i++) if((theMove=board.legal(player,i))!=-1)
             return theMove;
         return theMove;
		 */
		// Avec ordering
		int i = 1 + MOVE_INDEX[move>>Board.moveShift[Board.DIRECTIONS] & 127];
		for(; i<=MAX_MOVE ; i++){
			if ((theMove=board.legal(player,ORDERING[i]))!=-1){
				return theMove;
			}
		}
		return theMove;
	}

	/** Prochain coup alphabeta avec memoire */
	public double alphaBetaWM(int player,int currentDepth,int finalDepth,double alpha,double beta,int pass){
		// recuperation des valeurs dans la table de hachage 
		scannedPositions ++;
		int hashIndex=(int)(board.hashValue & hashKeyMask);
		if(hashLock[hashIndex]==board.hashValue && relativeLevel[hashIndex]==finalDepth-currentDepth){
			transpo++;
			boolean lb=false,ub=false;
			if((lb=(boundType[hashIndex]==LOWER_BOUND)) && score[hashIndex]>=beta)
				return score[hashIndex];
			if((ub=(boundType[hashIndex]==UPPER_BOUND)) && score[hashIndex] <= alpha)
				return score[hashIndex];
			if(lb)
				alpha=Math.max(alpha,score[hashIndex]);
			if(ub)
				beta=Math.min(beta,score[hashIndex]);
		}else if(hashLock[hashIndex]!=board.hashValue && relativeLevel[hashIndex]!=0)
			collision++;

		// Lorsqu'on arrive aux feuilles de l'arbre
		go=false;
		if(currentDepth==finalDepth || (go=(pass==2))){
			myDepth=currentDepth;
			return mobilite(player);
		}

		// Le truc 'complique'
		int theMove=0;
		int adv=1-player;
		double currentScore=-1e10;
		double a=alpha;
		boolean firstMove=true;
		while((theMove=nextMove(player,theMove))!=-1 || firstMove){
			if(theMove==-1)
				pass=pass==1?2:1;
			else
				pass=0;
			board.doMove(player,theMove);
			double theScore=-alphaBetaWM(adv,currentDepth+1,finalDepth,-beta,-a,pass);
			board.undoMove(player,theMove);
			firstMove = false;

			if(theScore>currentScore){
				currentScore=theScore;
				if(theMove!=1 && currentDepth==0)
					bestMove=theMove;
				if(theScore>a){
					a=theScore;
					if(a>=beta)
						break;
				}
			}
		}
		score[hashIndex]=currentScore;
		hashLock[hashIndex]=board.hashValue;
		relativeLevel[hashIndex]=finalDepth-currentDepth;
		if(currentScore<=alpha)
			boundType[hashIndex]=UPPER_BOUND;
		else if(currentScore >=beta)
			boundType[hashIndex]=LOWER_BOUND;
		else
			boundType[hashIndex]=EXACT_VALUE;
		return currentScore;
	}
	

	public double alphaBeta(int player,int currentDepth,int finalDepth,double alpha,double beta,int pass, int move){
		// r�cup�ration des valeurs dans la table de hachage 
		scannedPositions ++;
		int hashIndex=(int)(board.hashValue & hashKeyMask);
		if(hashLock[hashIndex]==board.hashValue && relativeLevel[hashIndex]==finalDepth-currentDepth)
			transpo++;
		else{
			hashLock[hashIndex]=board.hashValue;
			relativeLevel[hashIndex]=finalDepth-currentDepth;
		}

		// Lorsqu'on arrive aux feuilles de l'arbre
		go=false;
		if(currentDepth==finalDepth || (go=(pass==2))){
			myDepth=currentDepth;
			double heuristiques[]={mobilite(color), evalPositions(color), nombrePions(color)};
			r.setInput(heuristiques);
			//On renvoie la valeur du coup joue, represente par move qu'on transforme en un entier de 0 a 63
			return r.getOutput()[EncodeInt64(Board.decodePosition(move))];
		}

		// Le truc 'complique'
		int theMove=0;
		int adv=1-player;
		boolean firstMove=true;
		while((theMove=nextMove(player,theMove))!=-1 || firstMove){
			if(theMove==-1)
				pass=pass==1?2:1;
			else
				pass=0;
			
			board.doMove(player,theMove);
			double theScore=-alphaBeta(adv,currentDepth+1,finalDepth,-beta,-alpha,pass, theMove);
			board.undoMove(player,theMove);
			firstMove = false;

			if(theScore>alpha){
				alpha=theScore;
				if(theMove!=1 && currentDepth==0)
					bestMove=theMove;
				if(alpha>=beta)
					break;
			}
		}
		return alpha;
	}
	
	
	public double alphaBeta(int player,int currentDepth,int finalDepth,double alpha,double beta,int pass){
		// r�cup�ration des valeurs dans la table de hachage 
		scannedPositions ++;
		int hashIndex=(int)(board.hashValue & hashKeyMask);
		if(hashLock[hashIndex]==board.hashValue && relativeLevel[hashIndex]==finalDepth-currentDepth)
			transpo++;
		else{
			hashLock[hashIndex]=board.hashValue;
			relativeLevel[hashIndex]=finalDepth-currentDepth;
		}

		// Lorsqu'on arrive aux feuilles de l'arbre
		go=false;
		if(currentDepth==finalDepth || (go=(pass==2))){
			myDepth=currentDepth;
			return mobilite(player);
		}

		// Le truc 'complique'
		int theMove=0;
		int adv=1-player;
		boolean firstMove=true;
		while((theMove=nextMove(player,theMove))!=-1 || firstMove){
			if(theMove==-1)
				pass=pass==1?2:1;
			else
				pass=0;
			board.doMove(player,theMove);
			double theScore=-alphaBeta(adv,currentDepth+1,finalDepth,-beta,-alpha,pass, theMove);
			board.undoMove(player,theMove);
			firstMove = false;

			if(theScore>alpha){
				alpha=theScore;
				if(theMove!=1 && currentDepth==0)
					bestMove=theMove;
				if(alpha>=beta)
					break;
			}
		}
		return alpha;
	}
	
	
	/** Algorithme MTD */
	public double memoryTestDriver(int depth,double init){
		double g=init, beta;
		double ub=Integer.MAX_VALUE,lb=Integer.MIN_VALUE;
		int pass=0;
		do{
			if(g==lb)
				beta=g+1;
			else 
				beta=g;
			g=alphaBetaWM(color,0,depth,beta-1,beta,0);

			if(g<beta)
				ub=g;
			else
				lb=g;
			pass++;
		}while(lb<ub);
		System.out.println("MTD pass : " + pass);
		return g;
	}


	/** Methode qui renvoie le coup a jouer*/
	public int getMove(){
		if(!board.canMove(color))
			return -1;
		if(board.totalPieces<50)
			bestScore=alphaBeta(color,0,10,Integer.MIN_VALUE,Integer.MAX_VALUE,0);
		else
			bestScore=alphaBeta(color,0,20,Integer.MIN_VALUE,Integer.MAX_VALUE,0);
		/*
        if(board.totalPieces<48)
            bestScore=memoryTestDriver(10,bestScore);
        else
            bestScore=memoryTestDriver(60,bestScore);
		 */
		return bestMove;
	}

	public double evalPositions(int player){
		double resultat = 0;
		for(int i=Board.I_MIN;i<=Board.I_MAX;i++){
			if(board.board[i]==player)
				resultat += board.poids[i];
			else if(board.board[i]==(1-player))
				resultat -= board.poids[i];
		}
		return resultat;
	}
	
	public double nombrePions(int player){
			return board.material[player]-board.material[1-player];
	}
	
	public double mobilite(int player){
		//Nombre de coins possedes par les joueurs
		int cornerPlayer = 0;
		int cornerOpponent = 0;
		
		//Nombre de coups possibles pour les joueurs
		int mobilityPlayer = 0;
		int mobilityOpponent = 0;
		
		//On essaie de placer ses pions strategiquement pour marquer beaucoup de points en fin de partie
		if(board.board[Board.CORNER_NE] == player)
			cornerPlayer++;
		if(board.board[Board.CORNER_NE] == (1-player))
			cornerOpponent++;
		if(board.board[Board.CORNER_SE] == player)
			cornerPlayer++;
		if(board.board[Board.CORNER_SE] == (1-player))
			cornerOpponent++;
		if(board.board[Board.CORNER_SW] == player)
			cornerPlayer++;
		if(board.board[Board.CORNER_SW] == (1-player))
			cornerOpponent++;
		if(board.board[Board.CORNER_NW] == player)
			cornerPlayer++;
		if(board.board[Board.CORNER_NW] == (1-player))
			cornerOpponent++;
		
        for(int i=Board.I_MIN;i<=Board.I_MAX;i++){
        	if(board.legal(player,i)!=-1)
        		mobilityPlayer++;
        	else if(board.legal(1-player,i)!=-1)
        		mobilityOpponent++;
        }
		
		return Param.w1*(cornerPlayer - cornerOpponent) + Param.w2*(mobilityPlayer - mobilityOpponent)/(mobilityPlayer + mobilityOpponent);
			
	}

	public int jouerCoup(){
		String s=null;
		if(!board.canMove(color))
			return -1;
		try{
			s=parseur.next();
		} 
		catch(EOFException e)
		{ 
			System.out.println("La fin de fichier a ete atteinte.");
			return -2;
		}
		System.out.println("String renvoyee par parseur.next(): "+s);
		bestMove = Board.encodePosition(s); 
		return bestMove;
	}
	
	public int EncodeInt64(String coup){
		int coupInt = coup.charAt(0)%'A';
		coupInt += (coup.charAt(1)%'1')*8;
		return coupInt;
	}
}
